﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;

namespace OliveChess
{
    public class Position
    {
        const bool WHITE = true;
        const bool BLACK = false;

        private Board board;
        private Stack<GameState> gameStateStack;

        public Position()
        {
            board = new Board();
            gameStateStack = new Stack<GameState>();
        }

        public Position(ref Position position)
        {
            board = new Board(position.board);
            gameStateStack = position.gameStateStack;
        }

        public Position(string strPos)
        {
            board = new Board();
            gameStateStack = new Stack<GameState>();
            SetFromFEN(strPos);
        }

        public void SetFromFEN(String fenString)
        {
            board.Clear();
            gameStateStack.Clear();
            int square = 56;

            string[] parameters = fenString.Split(' ');
            String strBoard = parameters[0];
            char ply = parameters[1][0];
            String castling = parameters[2];
            String enPassant = parameters[3];
            String halfMoveClock = parameters[4];
            String fullMoveNumber = parameters[5];

            foreach (char carBoard in strBoard)
            {
                switch (carBoard)
                {
                    case 'K':
                        board.SetPiece(Piece.W_KING, square);
                        square++;
                        break;
                    case 'Q':
                        board.SetPiece(Piece.W_QUEEN, square);
                        square++;
                        break;
                    case 'R':
                        board.SetPiece(Piece.W_ROOK, square);
                        square++;
                        break;
                    case 'N':
                        board.SetPiece(Piece.W_KNIGHT, square);
                        square++;
                        break;
                    case 'B':
                        board.SetPiece(Piece.W_BISHOP, square);
                        square++;
                        break;
                    case 'P':
                        board.SetPiece(Piece.W_PAWN, square);
                        square++;
                        break;
                    case 'k':
                        board.SetPiece(Piece.B_KING, square);
                        square++;
                        break;
                    case 'q':
                        board.SetPiece(Piece.B_QUEEN, square);
                        square++;
                        break;
                    case 'r':
                        board.SetPiece(Piece.B_ROOK, square);
                        square++;
                        break;
                    case 'n':
                        board.SetPiece(Piece.B_KNIGHT, square);
                        square++;
                        break;
                    case 'b':
                        board.SetPiece(Piece.B_BISHOP, square);
                        square++;
                        break;
                    case 'p':
                        board.SetPiece(Piece.B_PAWN, square);
                        square++;
                        break;
                    case '/':
                        square -= 16;
                        break;
                    case '1':
                        square += 1;
                        break;
                    case '2':
                        square += 2;
                        break;
                    case '3':
                        square += 3;
                        break;
                    case '4':
                        square += 4;
                        break;
                    case '5':
                        square += 5;
                        break;
                    case '6':
                        square += 6;
                        break;
                    case '7':
                        square += 7;
                        break;
                    case '8':
                        square += 8;
                        break;
                    default:
                        // TODO Throw incorrect data exception
                        break;
                }
            }

            GameState gstate = new GameState();
            switch (ply)
            {
                case 'w':
                    gstate.turn = WHITE;
                    break;
                case 'b':
                    gstate.turn = BLACK;
                    break;
                default:
                    // TODO Throw incorrect data exception
                    break;
            }

            
            gstate.whiteCanCastleKingSide = false;
            gstate.whiteCanCastleQueenSide = false;
            gstate.blackCanCastleKingSide = false;
            gstate.blackCanCastleQueenSide = false;
            foreach (char carCastle in castling)
            {
                switch (carCastle)
                {
                    case 'K':
                        gstate.whiteCanCastleKingSide = true;
                        break;
                    case 'Q':
                        gstate.whiteCanCastleQueenSide = true;
                        break;
                    case 'k':
                        gstate.blackCanCastleKingSide = true;
                        break;
                    case 'q':
                        gstate.blackCanCastleQueenSide = true;
                        break;
                    default:
                        // TODO Throw incorrect data exception
                        break;
                }

            }

            if (enPassant[0] != '-')
            {
                gstate.lastEnPassant = (enPassant[0] - 'a') + 8 * (enPassant[1] - '1');
            }

            gstate.halfMoveClock = int.Parse(halfMoveClock);
            gstate.fullMoveNumber = int.Parse(fullMoveNumber);

            gameStateStack.Push(gstate);
        }

        public bool IsWhiteToMove()
        {
            return gameStateStack.Peek().turn;
        }

        public bool IsBlackToMove()
        {
            return !gameStateStack.Peek().turn;
        }
        
        public bool GetTurn()
        {
            return gameStateStack.Peek().turn;
        }

        /**
         * Indicates if the piece is playable for the given player (only check the
         * color)
         * 
         * @param square
         * @return
         */
        public bool IsPiecePlayable(int square)
        {
            Piece pieceCode = board.GetPiece(square);
            if (GetTurn())
            {
                return IsPieceWhite(pieceCode);
            }
            else
            {
                return IsPieceBlack(pieceCode);
            }
        }

        public bool IsPieceWhite(Piece piece)
        {
            return (piece == Piece.W_PAWN || piece == Piece.W_ROOK || piece == Piece.W_KNIGHT
                    || piece == Piece.W_BISHOP || piece == Piece.W_QUEEN || piece == Piece.W_KING);
        }

        public bool IsPieceBlack(Piece piece)
        {
            return (piece == Piece.B_PAWN || piece == Piece.B_ROOK || piece == Piece.B_KNIGHT
                    || piece == Piece.B_BISHOP || piece == Piece.B_QUEEN || piece == Piece.B_KING);
        }

        /**
         * Return if the square is empty or occupied by a piece of the opponent
         * 
         * @param square
         * @return
         */
        public bool IsEmptyOrOpponent(int square)
        {
            Piece piece = board.GetPiece(square);
            if (piece == Piece.NO_PIECE) return true;
            if (this.IsWhiteToMove()) return IsPieceBlack(piece);
            //if (this.IsBlackToMove()) return IsPieceWhite(piece);
            return IsPieceWhite(piece); //optim
        }
        public bool IsEmptyOrColor(int square, bool color)
        {
            Piece piece = this.GetPiece(square);
            if (piece == Piece.NO_PIECE) return true;
            if (color) return IsPieceWhite(piece);
            return IsPieceBlack(piece);
        }

        public Piece GetPiece(int square)
        {
            return board.GetPiece(square);
        }


        // Perform the move, augment gameStack and change turn
        public void Play(Move m)
        {
            Debug.Assert(this.gameStateStack.Count >= 1);
            Piece piece = m.piece;
            Piece cPiece = m.cPiece;
            
            GameState newGameState = new GameState();

            // En-passant extra-move (remove opponant's pawn)
            if (cPiece == Piece.NO_PIECE && Square.GetCol(m.from) != Square.GetCol(m.to))
            {
                if (piece == Piece.W_PAWN) board.SetPiece(Piece.NO_PIECE, m.to - 8);
                if (piece == Piece.B_PAWN) board.SetPiece(Piece.NO_PIECE, m.to + 8);
            }

            // Castleling extra-move (Rook)
            if (piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_G1) 
                board.Move(Square.SQ_H1, Square.SQ_F1);
            if (piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_C1) 
                board.Move(Square.SQ_A1, Square.SQ_D1);
            if (piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_G8) 
                board.Move(Square.SQ_H8, Square.SQ_F8);
            if (piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_C8) 
                board.Move(Square.SQ_A8, Square.SQ_D8);

            // Castleling - canceller
            // TODO: find a clean and quick way to update this status
            if (piece == Piece.W_KING)
            {
                newGameState.whiteCanCastleKingSide = false;
                newGameState.whiteCanCastleQueenSide = false;
            }
            else
                if (piece == Piece.B_KING)
                {
                    newGameState.blackCanCastleKingSide = false;
                    newGameState.blackCanCastleQueenSide = false;
                }
                else
                    if (piece == Piece.W_ROOK && m.from == Square.SQ_A1)
                    {
                        newGameState.whiteCanCastleQueenSide = false;
                    }
                    else
                        if (piece == Piece.W_ROOK && m.from == Square.SQ_H1)
                        {
                            newGameState.whiteCanCastleKingSide = false;
                        }
                        else
                            if (piece == Piece.B_ROOK && m.from == Square.SQ_A8)
                            {
                                newGameState.blackCanCastleQueenSide = false;
                            }
                            else
                                if (piece == Piece.B_ROOK && m.from == Square.SQ_H8)
                                {
                                    newGameState.blackCanCastleKingSide = false;
                                }

            // keeping en-passant last move
            if ((piece == Piece.W_PAWN && (Square.GetRow(m.to) - Square.GetRow(m.from)) == 2)
                || (piece == Piece.B_PAWN && (Square.GetRow(m.to) - Square.GetRow(m.from)) == 2))
            {
                newGameState.lastEnPassant = Math.Max(m.from, m.to) - Math.Min(m.from, m.to);
            }
            else
            {
                newGameState.lastEnPassant = Square.NO_SQUARE;
            }

            // standard move
            board.Move(m.from, m.to);

            // promotion
            bool turn = gameStateStack.Peek().turn;
            if (m.promotion != '0')
            {
                switch (m.promotion)
                {
                    case 'q':
                        if (turn) this.SetPiece(Piece.W_QUEEN, m.to);
                        else this.SetPiece(Piece.B_QUEEN, m.to);
                        break;
                    case 'r':
                        if (turn) this.SetPiece(Piece.W_ROOK, m.to);
                        else this.SetPiece(Piece.B_ROOK, m.to);
                        break;
                    case 'b':
                        if (turn) this.SetPiece(Piece.W_BISHOP, m.to);
                        else this.SetPiece(Piece.B_BISHOP, m.to);
                        break;
                    case 'n':
                        if (turn) this.SetPiece(Piece.W_KNIGHT, m.to);
                        else this.SetPiece(Piece.B_KNIGHT, m.to);
                        break;
                }

            }


            newGameState.turn = !turn;
            // 50 plies rule
            if (cPiece != Piece.NO_PIECE
                || piece == Piece.W_PAWN
                || piece == Piece.B_PAWN)
                newGameState.halfMoveClock = 0;
            else
                newGameState.halfMoveClock = gameStateStack.Peek().halfMoveClock + 1;
           // full move clock (+1 only after black ply)
            if (turn)
                newGameState.fullMoveNumber = gameStateStack.Peek().fullMoveNumber;
            else
                newGameState.fullMoveNumber = gameStateStack.Peek().fullMoveNumber + 1;

            // store pseudFen for 3-times repetition rule
            newGameState.pseudoFen = this.GetPseudoFEN();

            // push the new state in the gameStateStack
            gameStateStack.Push(newGameState);
        }

        public void Unplay(Move m)
        {
            Debug.Assert(this.gameStateStack.Count >= 2);
            // Unplay - base part
            board.SetPiece(m.piece,m.from);
            board.SetPiece(m.cPiece, m.to);

            // recover pre-casteling position
            if (m.piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_G1) 
                board.Move(Square.SQ_F1, Square.SQ_H1);
            if (m.piece == Piece.W_KING && m.from == Square.SQ_E1 && m.to == Square.SQ_C1) 
                board.Move(Square.SQ_D1, Square.SQ_A1);
            if (m.piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_G8) 
                board.Move(Square.SQ_F8, Square.SQ_H8);
            if (m.piece == Piece.B_KING && m.from == Square.SQ_E8 && m.to == Square.SQ_C8) 
                board.Move(Square.SQ_D8,Square.SQ_A8);

            // recover en-passant captured pawns
            // TODO: improve this code
            if (m.cPiece == Piece.NO_PIECE && Square.GetCol(m.from) != Square.GetCol(m.to))
            {
                if (m.piece == Piece.W_PAWN) board.SetPiece(Piece.B_PAWN, m.to - 8);
                if (m.piece == Piece.B_PAWN) board.SetPiece(Piece.W_PAWN, m.to + 8);
            }

            // Pop the state to return to the n-1 game state
            gameStateStack.Pop();
        }

        // TODO improve this method to implement the full do/undo move
        public void Move(byte from, byte to)
        {
            board.Move(from,to);
        }

        public void SetPiece(Piece piece, int square)
        {
            board.SetPiece(piece,square);
        }

        public void SetStartPosition()
        {
            this.SetFromFEN("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1");
        }

        // TODO impove this method
        public String GetSimplifiedHashCode()
        {
            /*
            StringBuilder result = new StringBuilder();
            for (int i = 0; i < 64; i++)
            {
                    result.Append((int)pieces[i]);
             }
                
            return result.ToString();*/
            return this.GetPseudoFEN();
        }


        private static char[] fenPieceCodes = { 'P', 'N', 'B', 'R', 'Q', 'K', 'p', 'n', 'b', 'r', 'q', 'k' };
        //private string strPos;

        /*
         * Unique key generator for positions
         */
        public String GetPseudoFEN()
        {
            StringBuilder result = new StringBuilder();
            int emptySquares = 0;
            for (int i = 0; i < 64; i++)
            {
                Piece p = board.pieces[i];
                if (p == Piece.NO_PIECE)
                {
                    emptySquares++;
                }
                else
                {
                    if (emptySquares > 0)
                    {
                        result.Append(emptySquares);
                        emptySquares = 0;
                    }
                    result.Append(fenPieceCodes[(int)p]);

                }
            }
            GameState gs = this.gameStateStack.Peek();
            result.Append(gs.turn);
            result.Append(gs.whiteCanCastleQueenSide ? '1' : '0');
            result.Append(gs.whiteCanCastleKingSide ? '1' : '0');
            result.Append(gs.blackCanCastleQueenSide ? '1' : '0');
            result.Append(gs.blackCanCastleKingSide ? '1' : '0');

            // TODO: see if this line is not too much restrictive
            // depth (= limit TT application to equal depth)
            //result.Append(gameStateStack.Count);

            return result.ToString();
        }


        internal bool IsSquareEmpty(int sq)
        {
            return board.IsSquareEmpty(sq);
        }

        internal bool WhiteCanCastleKingSide()
        {
            return gameStateStack.Peek().whiteCanCastleKingSide;
        }

        internal bool BlackCanCastleKingSide()
        {
            return gameStateStack.Peek().blackCanCastleKingSide;
        }

        internal bool WhiteCanCastleQueenSide()
        {
            return gameStateStack.Peek().whiteCanCastleQueenSide;
        }

        internal bool BlackCanCastleQueenSide()
        {
            return gameStateStack.Peek().blackCanCastleQueenSide;
        }

        internal int LastEnPassant()
        {
            return gameStateStack.Peek().lastEnPassant;
        }

        // for test only !!!
        public bool IsEqual(Position pos)
        {
            return board.IsEqual(pos.board) && IsEqual(gameStateStack,pos.gameStateStack);
        }

        // for test only !!!
        private static bool IsEqual(Stack<GameState> s1, Stack<GameState> s2)
        {
            Stack<GameState> st1 = new Stack<GameState>(s1); 
            Stack<GameState> st2 = new Stack<GameState>(s2); 

            while(st1.Count>0 && st2.Count>0)
            {
                GameState g1 = st1.Pop();
                GameState g2 = st2.Pop();
                if (!g1.IsEqual(g2)) return false;
            }

            return (st1.Count == 0) && (st2.Count == 0);
        }

        internal int Evaluate(ref int[] coeff)
        {
            return board.Evaluate(ref coeff);
        }


        internal int GetPlayedMoveNumber()
        {
            return gameStateStack.Count-1;
        }

        public int GetMovesWithoutCaptureOrPawnPush()
        {
            return gameStateStack.Peek().halfMoveClock;
        }

        internal object GetGameStateStack()
        {
            return gameStateStack;
        }

        internal bool IsThreeTimesRepetitionDraw()
        {
            Debug.Assert(gameStateStack.Count >= 6);
            GameState[] ags = gameStateStack.ToArray<GameState>();

            int top = ags.Length-1;

            return (ags[top].pseudoFen == ags[top - 2].pseudoFen)
                && (ags[top - 2].pseudoFen == ags[top - 4].pseudoFen);
        }
    }
}
